/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkClass } from 'arkanalyzer';
import { ModifierType } from 'arkanalyzer/lib/core/model/ArkBaseModel';
import { BaseChecker, BaseMetaData } from '../BaseChecker';
import { Rule, Defects, ClassMatcher, MatcherTypes, MatcherCallback } from '../../Index';
import { RuleListUtil } from '../../utils/common/DefectsList';
import { IssueReport } from '../../model/Defects';
import { NAME_DELIMITER } from 'arkanalyzer/lib/core/common/Const';
import { CONSTRUCTOR_NAME } from 'arkanalyzer/lib/core/common/TSConst';
import { ClassCategory } from 'arkanalyzer/lib/core/model/ArkClass';

const gMetaData: BaseMetaData = {
    severity: 2,
    ruleDocPath: 'docs/no-extraneous-class.md',
    description: 'Disallow classes used as namespaces'
};
type Options = {
    /** 是否允许只包含构造函数的类 */
    allowConstructorOnly?: boolean;
    /** 是否允许空类 */
    allowEmpty?: boolean;
    /** 是否允许只包含静态成员的类 */
    allowStaticOnly?: boolean;
    /** 是否允许带装饰器的类 */
    allowWithDecorator?: boolean;
};
type MessageIds = 'empty' | 'onlyConstructor' | 'onlyStatic';
const messages: Record<MessageIds, string> = {
    empty: 'Unexpected empty class.',
    onlyConstructor: 'Unexpected class with only a constructor.',
    onlyStatic: 'Unexpected class with only static properties.'
};
interface MemberFlags {
    hasConstructor: boolean;
    hasStaticMember: boolean;
    hasOtherMember: boolean;
}

export class NoExtraneousClassCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gMetaData;
    public rule: Rule;
    public issues: IssueReport[] = [];
    private defaultOptions: Options = {
        allowConstructorOnly: false,
        allowEmpty: false,
        allowStaticOnly: false,
        allowWithDecorator: false
    };
    private options: Options = { ...this.defaultOptions };
    private buildMatcher: ClassMatcher = {
        matcherType: MatcherTypes.CLASS,
        category: [ClassCategory.CLASS]
    };

    public registerMatchers(): MatcherCallback[] {
        const matchBuildCb: MatcherCallback = {
            matcher: this.buildMatcher,
            callback: this.check
        };
        return [matchBuildCb];
    };

    private parseOptions(): void {
        if (this.rule?.option?.[0]) {
            const ruleOption = this.rule.option[0] as Options;
            this.options = {
                allowConstructorOnly: ruleOption.allowConstructorOnly ?? this.defaultOptions.allowConstructorOnly,
                allowEmpty: ruleOption.allowEmpty ?? this.defaultOptions.allowEmpty,
                allowStaticOnly: ruleOption.allowStaticOnly ?? this.defaultOptions.allowStaticOnly,
                allowWithDecorator: ruleOption.allowWithDecorator ?? this.defaultOptions.allowWithDecorator
            };
        };
    };

    public check = (arkClass: ArkClass): void => {
        this.parseOptions();

        if (arkClass.isDefaultArkClass()) {
            return;
        }

        if (!!arkClass.getSuperClassName().length) {
            return;
        }

        if (arkClass.getDecorators().length > 0 && this.options.allowWithDecorator) {
            return;
        }

        const { hasConstructor, hasStaticMember, hasOtherMember } = this.getMemberFlags(arkClass);
        if (hasOtherMember) {
            return;
        }
        this.reportBasedOnFlags(arkClass, hasConstructor, hasStaticMember);
    };
        
    private getMemberFlags(arkClass: ArkClass): MemberFlags { 
        let hasConstructor = false;
        let hasStaticMember = false;
        let hasOtherMember = false;

        for (const arkMethod of arkClass.getMethods()) {
            if (arkMethod.isGenerated()) {
                continue;
            }
            if (arkMethod.getName() === CONSTRUCTOR_NAME) {
                hasConstructor = true;
            } else if (arkMethod.containsModifier(ModifierType.STATIC)) {
                hasStaticMember = true;
            } else {
                hasOtherMember = true;
                return { hasConstructor, hasStaticMember, hasOtherMember };
            }
        }

        for (const field of arkClass.getFields()) {
            if (field.containsModifier(ModifierType.STATIC)) {
                hasStaticMember = true;
            } else {
                hasOtherMember = true;
                return { hasConstructor, hasStaticMember, hasOtherMember };
            }
        }
        return { hasConstructor, hasStaticMember, hasOtherMember };
    }

    private reportBasedOnFlags(arkClass: ArkClass, hasConstructor: boolean, hasStaticMember: boolean): void {
        if (!hasConstructor && !hasStaticMember && !this.options.allowEmpty) {
            this.report(arkClass, 'empty');
        } else if (hasConstructor && !hasStaticMember && !this.options.allowConstructorOnly) {
            this.report(arkClass, 'onlyConstructor');
        } else if (hasStaticMember && !hasConstructor && !this.options.allowStaticOnly) {
            this.report(arkClass, 'onlyStatic');
        } else if (hasConstructor && hasStaticMember && !this.options.allowConstructorOnly && !this.options.allowStaticOnly) {
            this.report(arkClass, 'empty');
        }
    }

    private getCharacterOfPosition(className: string, code: string): number {
        const regex = new RegExp(`\\b${className}\\b`);
        const match = code.match(regex);
        return match?.index ?? 0;
    }

    private getArkClassPosition(arkClass: ArkClass): { line: number, startCol: number, endCol: number, filePath: string } {
        const name = arkClass.getName().split(NAME_DELIMITER)[0]; //类定义在方法里，拼接了一个后缀，标记它属于哪个方法
        const classNameStart = this.getCharacterOfPosition(name, arkClass.getCode() ?? '');
        return {
            line: arkClass.getLine(),
            startCol: arkClass.getColumn() + classNameStart,
            endCol: arkClass.getColumn() + classNameStart + (classNameStart !== 0 ? name.length : 0), // 匿名函数
            filePath: arkClass.getDeclaringArkFile().getFilePath()
        };
    }

    private report(arkClass: ArkClass, messageId: MessageIds): void {
        const severity = this.rule.alert ?? this.metaData.severity;
        const location = this.getArkClassPosition(arkClass);
        const defect = new Defects(
            location.line,
            location.startCol,
            location.endCol,
            messages[messageId],
            severity,
            this.rule.ruleId,
            location.filePath,
            this.metaData.ruleDocPath,
            true, false, false, false
        );
        this.issues.push(new IssueReport(defect, undefined));
        RuleListUtil.push(defect);
    }
}
